ES6 变量的解构赋值

变量的解构赋值

数组的解构赋值

  • ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

    1
    2
    3
    4
    5
    6
    7
    8
    // 以前,为变量赋值,只能直接指定值。
    let a = 1;
    let b = 2;
    let c = 3;

    ES6 允许写成下面这样。

    let [a, b, c] = [1, 2, 3]

    上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。

    本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。

    1
    2
    3
    4
    5
    6
    7
    8
    let [head, ...tail] = [1, 2, 3, 4];
    head // 1
    tail // [2, 3, 4]
    // es6 解构数组中的 ... 会匹配到的等号右边数组剩余的值,将其存入数组中。
    // 如果等号右边剩余的值为空,则解构出来的是空数组
    let [head, ...tail] = [1];
    head // 1
    tail // []
    1
    2
    3
    let [foo] = [];
    let [bar, foo] = [1];
    // 如果解构不成功,值为undefined

    另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

    1
    2
    3
    4
    5
    let [a, b, c] = [1, 2, 3, 4]
    a // 1
    b // 2
    c // 3
    // 上面两个例子,都属于不完全解构,但是可以成功

    可以发现,解构过程中,等号左边数组的length必须小于或等于等号右边的值(不考虑undefined的情况)

  • 默认值
    解构赋值允许指定默认值。

    1
    2
    let [foo = true] = [];
    foo // true

    注意:ES6 内部使用严格相等运算符(===),判断一个位置是否有值。
    所以,只有当一个数组成员严格等于undefined,默认值才会生效。
    也就是说,等号左边的某个值,解构相应位置等号右边的数组的值时,如果该值为空或者undefined,默认值才会生效
    如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let [foo = 1, bar = 2] = [3] 
    foo // 3
    bar // 2
    ========
    let [foo1 = 1, bar1 = 2] = []
    foo // 1
    bar // 2
    ========
    let [foo2 = 1, bar2 = 2] = [3, null]
    foo // 3
    bar // null // 此时null 不全等(===)undefined,所以默认值不生效

    如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

    1
    2
    3
    4
    function f() {
    console.log('aaa');
    }
    let [x = f()] = [1];

    上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。

    默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

    1
    2
    3
    4
    5
    let [x = 1, y = x] = [];      // x=1, y=1
    let [x = 1, y = x] = [2]; // x=2, y=2
    let [x = 1, y = x] = [1, 2]; // x=1, y=2
    let [x = y, y = 1] = []; // ReferenceError: y is not defined
    // 最后一个表达式之所以会报错,是因为x用y做默认值时,但y还没有声明。

对象的解构赋值

  • 解构不仅可以用于数组,还可以用于对象。

    1
    2
    3
    let {foo, bar} = {foo: 'aaa', bar: 'bbb'}
    foo // "aaa"
    bar // "bbb"

    在不使用对象解构时,我们一般是如下获取对象的value的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    let ajaxData = { 
    id: 10086,
    name: 'bob',
    age: '28'
    }
    var id = ajaxData.id
    var name = ajaxData.name
    var age = ajaxData.age

    使用对象解构后就很简洁了
    let ajaxData = {
    id: 10086,
    name: 'bob',
    age: '28'
    }
    let {id, name, age} = ajaxData;
    id // 10086
    name // 'bob'
    age // '28'
    // 如果要使用的变量就是等号右侧对象中的key,
    // 在上面的例子中,我要使用的就是 id, name, age, 然后把对象key对应的value赋值即可,
    // 就可以采用简写形式 let {id, name, age} = ajaxData;
    // 即 如果你最终要声明变量的key 与等号右边的对象中的key相同,可以缩写为 { key1, key2, ... }

    // 但如果我要更改变量的名称,可以写成如下形式
    let {id: a, name: b, age: c} = ajaxData;
    // 那么获取的时候就是a, b, c, 即
    a // 10086
    b // 'bob'
    c // '28'

    也就是说,如果变量名与属性名不一致,必须写成下面这样。

    1
    2
    3
    4
    5
    6
    7
    let { foo: baz } = {foo: 'aaa'}
    baz // aaa

    let obj = { first: 'hello', last: 'world' }
    let { first: f, last: l } = obj
    f // 'hello'
    l // 'world'

    这实际上说明,对象的解构赋值是下面形式的简写

    1
    let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

    也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。
    真正被赋值的是后者,而不是前者。

    对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;
    而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

    1
    2
    3
    4
    5
    6
    7
    8
    let {bar, foo} = {foo: 'aaa', bar: 'bbb'}
    foo // aaa
    bar // bbb
    // 等号两边变量次序不一致,但是取值时并没有收到影响

    let {bar, foo} = {foo: 'aaa', bar: 'bbb'}
    baz // undefined
    // 等号右边并没有对应的同名属性baz,所以取不到值,值为undefined
  • 变量的解构赋值的用途
    (1)交换变量的值

    1
    2
    3
    4
    5
    6
    let x = 1;
    let y = 2;

    [x, y] = [y, x]

    // 上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。

    (2)从函数返回多个值

    函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 返回一个数组
    function example() {
    return [1, 2, 3];
    }
    let [a, b, c] = example();

    // 返回一个对象
    function example() {
    return {
    foo: 1,
    bar: 2
    };
    }
    let { foo, bar } = example();

    (3)函数参数的定义

    解构赋值可以方便地将一组参数与变量名对应起来。

    1
    2
    3
    4
    5
    6
    7
    // 参数是一组有次序的值
    function f([x, y, z]) { ... }
    f([1, 2, 3]);

    // 参数是一组无次序的值
    function f({x, y, z}) { ... }
    f({z: 3, y: 2, x: 1});

    (4)提取 JSON 数据

    解构赋值对提取 JSON 对象中的数据,尤其有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let jsonData = {
    id: 42,
    status: "OK",
    data: [867, 5309]
    };

    let { id, status, data: number } = jsonData;

    console.log(id, status, number);
    // 42, "OK", [867, 5309]

    (5)函数参数的默认值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    jQuery.ajax = function (url, {
    async = true,
    beforeSend = function () {},
    cache = true,
    complete = function () {},
    crossDomain = false,
    global = true,
    // ... more config
    } = {}) {
    // ... do stuff
    };

    指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句。

    (6)输入模块的指定方法

    加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

    1
    const { SourceMapConsumer, SourceNode } = require("source-map");